﻿using Smile.Converter;
using Smile.Network.Http;
using System;
using System.Collections.Generic;
using System.Net;
using System.Threading.Tasks;
using Windows.Security.Authentication.Web;
using Windows.Security.Cryptography;
using Windows.Security.Cryptography.Core;
using Windows.Storage.Streams;

namespace Smile.Network.OAuth
{
    public abstract class OAuthClientBaseV1 : OAuthClientBase
    {
        // @Properties
        public string OAuthRequestTokenUrl
        {
            get
            {
                return _OAuthRequestTokenUrl;
            }
            protected set
            {
                _OAuthRequestTokenUrl = value;
            }
        }

        public string OAuthAccessTokenUrl
        {
            get
            {
                return _OAuthAccessTokenUrl;
            }
            protected set
            {
                _OAuthAccessTokenUrl = value;
            }
        }

        public string OAuthAuthorizeUrl
        {
            get
            {
                return _OAuthAuthorizeUrl;
            }
            protected set
            {
                _OAuthAuthorizeUrl = value;
            }
        }

        public string RequestToken
        {
            get
            {
                return _requestToken;
            }
            protected set
            {
                _requestToken = value;
            }
        }

        public string RequestTokenSecret
        {
            get
            {
                return _requestTokenSecret;
            }
            protected set
            {
                _requestTokenSecret = value;
            }
        }

        public string RequestVerifier
        {
            get
            {
                return _requestVerifier;
            }
            protected set
            {
                _requestVerifier = value;
            }
        }

        public Uri RequestCallback
        {
            get
            {
                return _requestCallback;
            }
            protected set
            {
                _requestCallback = value;
            }
        }


        // @Public
        public OAuthClientBaseV1(string consumerKey, string consumerSecret, string callbackUriString)
            : this(consumerKey, consumerSecret, new Uri(callbackUriString))
        {
        }

        public OAuthClientBaseV1(string consumerKey, string consumerSecret, Uri callbackUri) 
            : base(consumerKey, consumerSecret)
        {
            RequestCallback = callbackUri;
            InitializeUri();
        }

        public async override Task<bool> AuthenticateAsync()
        {
            // Initialize data.
            RequestToken = "";
            RequestTokenSecret = "";

            // Request token
            if (await GetRequestTokenAsync())
            {
                // Authorize code
                if (await GetVerifyCode())
                {
                    // Access token
                    return await GetAccessTokenAsync();
                }
            }
            return false;
        }


        // @Protected
        protected string    _OAuthRequestTokenUrl = "";
        protected string    _OAuthAccessTokenUrl = "";
        protected string    _OAuthAuthorizeUrl = "";
        protected string    _requestToken = "";
        protected string    _requestTokenSecret = "";
        protected string    _requestVerifier = "";
        protected Uri       _requestCallback;

        protected abstract void InitializeUri();

        protected string GetSignature(string uriString, Dictionary<string, string> baseParams)
        {
            string signInBaseString = "POST&"
                    + Uri.EscapeDataString(uriString)
                    + "&"
                    + Uri.EscapeDataString(HttpRequest.GenerateDataStringContent(baseParams));

            var signInKey = string.Format(
                "{0}&{1}",
                ConsumerSecret,
                RequestTokenSecret);

            // Econcoding signature.
            IBuffer keyMaterial = CryptographicBuffer.ConvertStringToBinary(signInKey, BinaryStringEncoding.Utf8);
            var hmacSha1Provider = MacAlgorithmProvider.OpenAlgorithm("HMAC_SHA1");
            CryptographicKey macKey = hmacSha1Provider.CreateKey(keyMaterial);
            IBuffer dataToBeSigned = CryptographicBuffer.ConvertStringToBinary(signInBaseString, BinaryStringEncoding.Utf8);
            IBuffer signatureBuffer = CryptographicEngine.Sign(macKey, dataToBeSigned);
            String signature = CryptographicBuffer.EncodeToBase64String(signatureBuffer);
            return signature; 
        }

        protected virtual async Task<bool> GetRequestTokenAsync()
        {
            string nonce = GetNonce();
            string timestamp = DateTimeConverter
                .ToUnixTimeStamp(DateTime.Now)
                .ToString();

            // Complete request params
            var baseParams = new Dictionary<string, string>()
            {
                {"oauth_consumer_key", ConsumerKey},
                {"oauth_nonce", nonce},
                {"oauth_signature_method", "HMAC-SHA1"},
                {"oauth_timestamp", timestamp},
                {"oauth_version", "1.0"}
            };
            baseParams.Add("oauth_signature", GetSignature(OAuthRequestTokenUrl, baseParams));

            // Request servers.
            var request = new HttpRequest();
            var response = await request.PostAsync(OAuthRequestTokenUrl, baseParams);
            if (response.Status == HttpStatusCode.OK)
            {
                // Parsing response
                var responseParam = response
                    .Content
                    .Split('&');
                for (int i = 0; i < responseParam.Length; ++i)
                {
                    var responseValues = responseParam[i].Split('=');
                    switch (responseValues[0])
                    {
                        case "oauth_token":
                            RequestToken = responseValues[1];
                            break;
                        case "oauth_token_secret":
                            RequestTokenSecret = responseValues[1];
                            break;
                    }
                }
                return true;
            }
            return false;
        }

        protected async Task<bool> GetVerifyCode()
        {
            try
            {
                var wabr = await WebAuthenticationBroker.AuthenticateAsync(
                    WebAuthenticationOptions.None,
                    new Uri(OAuthAuthorizeUrl + "?oauth_token=" + RequestToken),
                    RequestCallback);
                if (wabr.ResponseStatus == WebAuthenticationStatus.Success)
                {
                    // Parsing response
                    var responseParam = wabr
                        .ResponseData
                        .Split('&');
                    for (int i = 0; i < responseParam.Length; ++i)
                    {
                        var responseValues = responseParam[i].Split('=');
                        switch (responseValues[0])
                        {
                            case "oauth_token":
                                if (responseValues[1] != RequestToken)
                                {
                                    return false;
                                }
                                break;
                            case "oauth_verifier":
                                RequestVerifier = responseValues[1];
                                break;
                        }
                    }
                    return true;
                }
            }
            catch (Exception)
            {
            }
            return false;

        }

        protected async Task<bool> GetAccessTokenAsync()
        {
            string nonce = GetNonce();
            string timestamp = DateTimeConverter
                .ToUnixTimeStamp(DateTime.Now)
                .ToString();

            // Complete request params
            var baseParams = new Dictionary<string, string>()
            {
                {"oauth_consumer_key", ConsumerKey},
                {"oauth_nonce", nonce},
                {"oauth_signature_method", "HMAC-SHA1"},
                {"oauth_timestamp", timestamp},
                {"oauth_token", RequestToken},
                {"oauth_version", "1.0"},
                {"oauth_verifier", RequestVerifier}
            };
            baseParams.Add("oauth_signature", GetSignature(OAuthAccessTokenUrl, baseParams));

            // Request Twitter servers.
            var request = new HttpRequest();
            var response = await request.PostAsync(OAuthAccessTokenUrl, baseParams);
            if (response.Status == HttpStatusCode.OK)
            {
                // Parsing response
                var responseParam = response
                    .Content
                    .Split('&');
                for (int i = 0; i < responseParam.Length; ++i)
                {
                    var responseValues = responseParam[i].Split('=');
                    switch (responseValues[0])
                    {
                        case "oauth_token":
                            RequestToken = responseValues[1];
                            Token = RequestToken;
                            break;
                        case "oauth_token_secret":
                            RequestTokenSecret = responseValues[1];
                            break;
                        case "user_id":
                            UserId = responseValues[1];
                            break;
                    }
                }
                return true;
            }

            return false;
        }

        protected string GetNonce()
        {
            var rand = new Random();
            return rand
                .Next(1000000000)
                .ToString();
        }
    }
}
